package cn.legym.garp.gateway.traffic.dynamic.bean;

import cn.legym.garp.gateway.traffic.ParamSourceParser;
import cn.legym.garp.gateway.traffic.rule.TrafficRule;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;

/**
 * create by pipisun on 2021/9/6
 * copyright © 2017-2021 Legym Technology Co.,Ltd. All rights reserve
 * d.
 * file description:
 * <p>
 * 自定义流控资源的元数据——流控资源定义
 * <p>
 * last update by {} on {}
 * update description:
 */
@Data
public class DynamicResourceDefinition {

    /**
     * 自定义资源名称 必填项
     */
    private String name;

    /**
     * 请求参数查找器 对请求中的参数进行匹配及定位
     * <p>
     * 全部符合则认为请求匹配到了此资源。集合为空时直接返回成功。
     */
    private List<DynamicParam> params;

    /**
     * Sentinel Rule Definition 必填项。内容为一个Sentinel规则数据
     */
    private RuleMeta meta;

    /**
     * 资源匹配
     * <p>
     * 当一个请求符合某个自定义流量规则({@link cn.legym.garp.gateway.traffic.rule.TrafficRule})后，
     * 会继续通过此方法检查请求内参数是否匹配。
     * <p>
     * 在调用此方法前，通过{@link TrafficRule#getResource()} 和 {@link DynamicResourceDefinition#name} 比对找到合适的资源定义，
     * 继而调用此方法，依次检查参数匹配情况。最终返回匹配结果。
     * <p>
     * 匹配方式包括：
     * <li>请求路径参数匹配(GET适用)</li>
     * <li>请求restful路径正则匹配</li>
     * <li>请求Header参数匹配</li>
     * <li>请求RequestBody内容JSON键值匹配</li>
     * 在 {@link cn.legym.garp.gateway.traffic.ParamSourceType} 中查看更多定义
     *
     * @param context spring application context
     * @param request 请求
     * @return 匹配结果，如果成功，会返回一个从请求（request)中解析出的参数名和参数值的Tuple集合({@link MatchResult#results})
     * @see cn.legym.garp.gateway.traffic.rule.TrafficRuleManager#matchRule(ServerHttpRequest)
     * @see cn.legym.garp.gateway.traffic.dynamic.DynamicResourceManager#locateResource(ServerHttpRequest, TrafficRule)
     * @see cn.legym.garp.gateway.traffic.ParamSourceType
     */
    public MatchResult matchRequest(ApplicationContext context, ServerHttpRequest request) {
        if (CollectionUtils.isEmpty(params)) {
            return MatchResult.success(Collections.emptyList());
        }
        List<ParamMatchResult> results = Lists.newArrayList();
        for (DynamicParam p : params) {
            ParamSourceParser parser = context.getBean(p.getFrom().getParserClazz());
            String paramValue = parser.parse(request, p.getKey());
            if (paramValue == null) {
                return MatchResult.fail();
            }
            results.add(ParamMatchResult.success(p.getKey(), paramValue));
        }
        return MatchResult.success(results);
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ParamMatchResult {
        private String key;
        private String value;

        public static ParamMatchResult success(String key, String value) {
            return new ParamMatchResult(key, value);
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class MatchResult {

        private boolean success;
        private List<ParamMatchResult> results;

        public static MatchResult fail() {
            return new MatchResult();
        }

        public static MatchResult success(List<ParamMatchResult> results) {
            return new MatchResult(true, results);
        }

    }
}
